﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Xml;
using System.Xml.Serialization;
using TeachMe.Application.Exceptions;

namespace TeachMe.Application
{
    using System.IO;
    using System.Text;

    public class XmlEditor
    {
        private class LoadedNode : EasyDispose
        {
            private readonly XmlEditor _editor;

            public LoadedNode(XmlEditor editor, XmlNode node)
            {
                _editor = editor;
                _editor.Push(node);
            }

            protected override void Disposing()
            {
                HandleDeleteRequests();

                _editor.Pop();
            }

            private void HandleDeleteRequests()
            {
                List<XmlNode> deletedNodes = null;

                foreach (XmlNode nodeToDelete in _editor.NodesToDelete)
                {
                    if (nodeToDelete.ParentNode == _editor.Peek())
                    {
                        if (deletedNodes == null)
                        {
                            deletedNodes = new List<XmlNode>();
                        }

                        deletedNodes.Add(nodeToDelete);

                        _editor.Peek().RemoveChild(nodeToDelete);
                    }
                }

                if (deletedNodes != null)
                {
                    foreach (XmlNode deletedNode in deletedNodes)
                    {
                        _editor.NodesToDelete.Remove(deletedNode);
                    }
                }
            }
        }

        private readonly XmlDocument _document = new XmlDocument();
        private readonly Stack<XmlNode> _nodes = new Stack<XmlNode>();
        private readonly List<XmlNode> _nodesToDelete = new List<XmlNode>();

        private XmlEditor()
        {
            //
        }

        private List<XmlNode> NodesToDelete
        {
            get { return _nodesToDelete; }
        }

        [XmlIgnore]
        public string Path { get; private set; }

        public static XmlEditor Load(string path)
        {
            XmlEditor result = new XmlEditor();
            result.Path = path;

            try
            {
                result._document.Load(path);
            }
            catch (Exception)
            {
                try
                {
                    // try default encoding
                    using (TextReader reader = new StreamReader(path, Encoding.Default))
                    {
                        result._document.Load(reader);
                    }
                }
                catch (Exception e)
                {
                    throw new XmlEditorException(String.Format("File '{0}' could not be loaded", path), e);
                }
            }

            result._nodes.Push(result._document.DocumentElement);

            return result;
        }

        public void Save()
        {
            try
            {
                _document.Save(Path);
            }
            catch (Exception e)
            {
                throw new XmlEditorException(String.Format("File '{0}' could not be saved", Path), e);
            }
        }

        public bool CheckVersion(int version)
        {
            if (_document.DocumentElement == null)
            {
                return false;
            }

            if (_document.DocumentElement.Attributes["version"] == null)
            {
                return false;
            }

            return version == Int32.Parse(_document.DocumentElement.Attributes["version"].Value);
        }

        public IDisposable LoadNode(string name)
        {
            return new LoadedNode(this, FindChildNode(name));
        }

        public bool HasChildNode(string name)
        {
            return FindChildNode(name) != null;
        }

        public string GetTextValue()
        {
            return _nodes.Peek().InnerText;
        }

        public void SetTextValue(string value)
        {
            _nodes.Peek().InnerText = value;
        }

        public Color GetColorValue()
        {
            return Color.FromName(GetTextValue());
        }

        public float GetFloatValue()
        {
            return float.Parse(GetTextValue());
        }

        public int GetIntValue()
        {
            return int.Parse(GetTextValue());
        }

        public T GetEnumValue<T>()
        {
            return (T)Enum.Parse(typeof(T), GetTextValue());
        }

        public string GetAttributeValue(string name)
        {
            return _nodes.Peek().Attributes[name].Value;
        }

        public IEnumerable<IDisposable> LoadChildNodes()
        {
            if (_nodes.Peek().HasChildNodes)
            {
                foreach (XmlNode node in _nodes.Peek().ChildNodes)
                {
                    yield return new LoadedNode(this, node);
                }
            }
        }

        private void Push(XmlNode node)
        {
            _nodes.Push(node);
        }

        private void Pop()
        {
            _nodes.Pop();
        }

        private XmlNode Peek()
        {
            return _nodes.Peek();
        }

        private XmlNode FindChildNode(string name)
        {
            if (_nodes.Peek().HasChildNodes)
            {
                foreach (XmlNode node in _nodes.Peek().ChildNodes)
                {
                    if (node.NodeType == XmlNodeType.Element && node.Name == name)
                    {
                        return node;
                    }
                }
            }
            return null;
        }

        public IDisposable AddNode(string name)
        {
            XmlElement node = _document.CreateElement(name);

            _nodes.Peek().AppendChild(node);

            return new LoadedNode(this, node);
        }

        public static XmlEditor Create(string path, string rootElementName)
        {
            XmlEditor editor = new XmlEditor();
            editor.Path = path;

            XmlElement rootElement = editor._document.CreateElement(rootElementName);
            editor._document.AppendChild(rootElement);
            editor._nodes.Push(rootElement);

            return editor;
        }

        public void SetAttributeValue(string name, string value)
        {
            XmlAttribute attribute = _document.CreateAttribute(name);
            attribute.Value = value;

            _nodes.Peek().Attributes.Append(attribute);
        }

        public IDisposable LoadNode(string name, bool create)
        {
            XmlNode node = FindChildNode(name);

            if (node == null && create)
            {
                node = _document.CreateElement(name);
                _nodes.Peek().AppendChild(node);
            }

            return new LoadedNode(this, node);
        }

        public void RemoveChildNode(string name)
        {
            _nodes.Peek().RemoveChild(FindChildNode(name));
        }

        public bool HasAttributeValue(string name)
        {
            return _nodes.Peek().Attributes[name] != null;
        }

        public void RemoveNode()
        {
            NodesToDelete.Add(_nodes.Peek());
        }
    }
}
